﻿using System;
using System.Collections;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.Networking;

namespace FutureCorePlugin
{
    public class AssemblyLauncher : MonoBehaviour
    {
        private enum DownType
        {
            Version,
            Dll,
        }

        private long webReqSucceedCode = 200;
        private bool isWebError;

        private string internalDllPath = "Plugins/ProjectGApp/Project.GApp.dll";
        private string webDllParentPath = "ProjectGApp";
        private string webDllFilePath = "ProjectGApp/Project.GApp.bytes";
        private string webDllVersionFilePath = "ProjectGApp/version.txt";
        private string webDllPDataPath;
        private string dllWebUrl;
        private string webDllVersionPDataPath;
        private string dllVersionWebUrl;

        private static Assembly GAssembly;

        private void Awake()
        {
            internalDllPath = Application.dataPath + "/" + internalDllPath;
            webDllParentPath = Application.persistentDataPath + "/" + webDllParentPath;
            webDllPDataPath = Application.persistentDataPath + "/" + webDllFilePath;
            dllWebUrl = AssemblyConst.DllWebUrl + webDllFilePath;
            webDllVersionPDataPath = Application.persistentDataPath + "/" + webDllVersionFilePath;
            dllVersionWebUrl = AssemblyConst.DllWebUrl + webDllVersionFilePath;

            Launcher();
        }

        private void Launcher()
        {
            if (!AssemblyConst.IsRDG)
            {
#if C_GAME_RAW_DLL
                Main.EnterMain();
#else
                EnterInternalDll();
#endif
            }
            else
            {
                StartCoroutine(EnterWebDll());
            }
        }

        private void EnterInternalDll()
        {
            byte[] dllBytes = LoadDllFromFile(internalDllPath);
            StartUpDllProcess(dllBytes);
        }

        private IEnumerator EnterWebDll()
        {
            isWebError = false;

            yield return HotUpdate();
            if (isWebError) yield break;

            byte[] dllBytes = LoadDllFromFile(webDllPDataPath);
            StartUpDllProcess(dllBytes);
        }

        private IEnumerator HotUpdate()
        {
            // Version
            NewEnviroment();
            AssemblyConst.LocalVersion = LoadVersionFormFile(webDllVersionPDataPath);
            yield return StartCoroutine(OnWebDown(DownType.Version, dllVersionWebUrl, webDllVersionPDataPath));
            if (isWebError) yield break;
            AssemblyConst.WebVersion = LoadVersionFormFile(webDllVersionPDataPath);
            DebugUtil.LogFormat("[AssemblyLauncher]LocalVersion:{0} WebVersion:{1}", AssemblyConst.LocalVersion, AssemblyConst.WebVersion);

            // Dll
            if (AssemblyConst.WebVersion > AssemblyConst.LocalVersion || !File.Exists(webDllPDataPath))
            {
                if (File.Exists(webDllPDataPath)) File.Delete(webDllPDataPath);
                yield return StartCoroutine(OnWebDown(DownType.Dll, dllWebUrl, webDllPDataPath));
                DebugUtil.Log("[AssemblyLauncher]HotUpdate Dll");
            }
        }

        private void NewEnviroment()
        {
            if (!Directory.Exists(Application.persistentDataPath))
            {
                Directory.CreateDirectory(Application.persistentDataPath);
            }
            if (!Directory.Exists(webDllParentPath))
            {
                Directory.CreateDirectory(webDllParentPath);
            }
        }

        private IEnumerator OnWebDown(DownType type, string webUrl, string savePath)
        {
            DebugUtil.LogFormat("[AssemblyLauncher]Web Down:{0}", webUrl);
            UnityWebRequest webReq = UnityWebRequest.Get(webUrl);
            yield return webReq.Send();
            if (webReq.isError || webReq.responseCode != webReqSucceedCode)
            {
                isWebError = true;
                OpenErrorView(webReq);
                DebugUtil.LogErrorFormat("[AssemblyLauncher]WebDown Error:{0} {1}", webReq.responseCode, webReq.error);
                yield break;
            }
            SaveDataInFile(savePath, webReq.downloadHandler.data);
            webReq.Dispose();
        }

        private void OpenErrorView(UnityWebRequest webReq)
        {
            PreGameSplashUI.Instance.Close();
            PreLoadingUI.Instance.Open();

            PreSystemAffirmUI.ViewInfo info = new PreSystemAffirmUI.ViewInfo();
            if (webReq.isError)
                info.contentInfo = "网络连接失败，请重试。";
            else
                info.contentInfo = "游戏连接失败，请重试。";
            info.affirmBtnInfo = "重试";
            info.affirmFunc = () => { StartCoroutine(EnterWebDll()); };
            info.cancelBtnInfo = "退出";
            info.cancelFunc = App.Quit;
            PreSystemAffirmUI.Instance.Open(info);
        }

        private void SaveDataInFile(string path, byte[] bytes)
        {
            if (File.Exists(path))
            {
                File.Delete(path);
            }
            File.WriteAllBytes(path, bytes);
        }

        private byte[] LoadDllFromFile(string dllPath)
        {
            DebugUtil.Log("[AssemblyLauncher]Load Dll From File");
            if (!File.Exists(dllPath))
            {
                DebugUtil.LogFormat("[AssemblyLauncher]Load Dll From File Is Non Exists: {0}", dllPath);
                return null;
            }

            byte[] dllBytes = File.ReadAllBytes(dllPath);
            return dllBytes;
        }

        private int LoadVersionFormFile(string versionPath)
        {
            DebugUtil.Log("[AssemblyLauncher]Load Version From File");
            if (!File.Exists(versionPath))
            {
                DebugUtil.LogFormat("[AssemblyLauncher]Load Version From File Is Non Exists: {0}", versionPath);
                return 0;
            }

            string versionText = File.ReadAllText(versionPath).Trim();
            int version = 0;
            int.TryParse(versionText, out version);
            return version;
        }

        private void StartUpDllProcess(byte[] dllBytes)
        {
            DecodeDll(dllBytes);
            InvokeDll(dllBytes);
        }

        private void DecodeDll(byte[] dllBytes)
        {
            DebugUtil.Log("[AssemblyLauncher]Decode Dll");
            // TODO 解密DLL (正式上线再处理)
        }

        private void InvokeDll(byte[] dllBytes)
        {
            DebugUtil.Log("[AssemblyLauncher]Invoke Dll");
            GAssembly = Assembly.Load(dllBytes);
            Type mainType = GAssembly.GetType("Main");
            MethodInfo funcInfo = mainType.GetMethod("EnterMain", BindingFlags.Public | BindingFlags.Static);
            funcInfo.Invoke(null, null);
        }
    }
}